Este documento presenta un análisis comparativo entre las predicciones generadas por un modelo de OpenAI y los datos manuales de referencia en el contexto de la clasificación radiológica BIRADS. El objetivo es evaluar la precisión y fiabilidad del modelo para clasificar hallazgos radiológicos según el sistema BI-RADS (Breast Imaging-Reporting and Data System).
Este análisis se basa en dos conjuntos de datos: 1. Datos de referencia manual: Clasificaciones realizadas por profesionales médicos 2. Predicciones de OpenAI: Clasificaciones generadas por el modelo de OpenAI
# Cargar datos
manual_data <- read_delim("DataManual.csv", delim = ";", escape_double = FALSE, locale = locale(encoding = "UTF-8"), trim_ws = TRUE)
openai_data <- read_delim("1respuestaOpenAI.csv", delim = ";", escape_double = FALSE, locale = locale(encoding = "UTF-8"), trim_ws = TRUE)
# Convertir nombres de columnas a un formato uniforme para facilitar comparaciones
names(manual_data) <- tolower(names(manual_data))
names(manual_data) <- gsub(" ", "_", names(manual_data))
# Mostrar las primeras filas de ambos conjuntos de datos
kable(head(manual_data, 5), caption = "Primeras 5 filas de los datos manuales") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"), font_size = 11)| nodulos | morfologia_de_los_nodulos | margenes_nodulos | densidad_nodulo | presencia_microcalcificaciones | calcificaciones_tipicamente_benignas | calcififcaciones_morfologia_sospechosa | distribucion_de_las_calcificaciones | presencia_de_asimetrias | tipo_de_asimetria | hallazgos_asociados | lateralidad_hallazgo | birads |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | NA | NA | NA | 1 | 5 | NA | 2 | NA | NA | NA | 1 | 4 |
| 0 | NA | NA | NA | 0 | NA | NA | NA | 0 | NA | 5 | 1 | 2 |
| 0 | NA | NA | NA | 1 | NA | NA | NA | NA | NA | NA | 1 | 2 |
| 0 | NA | NA | NA | 1 | 5 | NA | 2 | NA | NA | NA | 1 | 2 |
| 0 | NA | NA | NA | 1 | 5 | NA | NA | 0 | NA | NA | 1 | 2 |
kable(head(openai_data, 5), caption = "Primeras 5 filas de las predicciones de OpenAI") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"), font_size = 11)| nodulos | morfologia_nodulos | margenes_nodulos | densidad_nodulo | microcalcificaciones | calcificaciones_benignas | calcificaciones_sospechosas | distribucion_calcificaciones | presencia_asimetrias | tipo_asimetria | hallazgos_asociados | lateralidad_hallazgo | birads |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | NA | NA | NA | 0 | NA | NA | NA | 0 | NA | NA | 3 | 2 |
| 0 | NA | NA | NA | 0 | NA | NA | NA | 0 | NA | NA | 1 | 2 |
| 0 | NA | NA | NA | 0 | NA | NA | NA | 0 | NA | NA | 1 | 2 |
| 0 | NA | NA | NA | 0 | NA | NA | NA | 0 | NA | NA | 1 | 2 |
| 0 | NA | NA | NA | 0 | NA | NA | NA | 1 | 2 | 1 | 3 | 2 |
Ambos conjuntos contienen 695 registros con 13 columnas que representan diferentes características radiológicas:
# Mostrar estructura de los datos
estructura_manual <- data.frame(
Variable = names(manual_data),
Clase = sapply(manual_data, class),
Valores_Únicos = sapply(manual_data, function(x) length(unique(x))),
Valores_NA = sapply(manual_data, function(x) sum(is.na(x)))
)
kable(estructura_manual, caption = "Estructura de los datos manuales") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"))| Variable | Clase | Valores_Únicos | Valores_NA | |
|---|---|---|---|---|
| nodulos | nodulos | numeric | 3 | 81 |
| morfologia_de_los_nodulos | morfologia_de_los_nodulos | numeric | 4 | 611 |
| margenes_nodulos | margenes_nodulos | numeric | 5 | 637 |
| densidad_nodulo | densidad_nodulo | numeric | 5 | 612 |
| presencia_microcalcificaciones | presencia_microcalcificaciones | numeric | 3 | 77 |
| calcificaciones_tipicamente_benignas | calcificaciones_tipicamente_benignas | numeric | 6 | 178 |
| calcififcaciones_morfologia_sospechosa | calcififcaciones_morfologia_sospechosa | numeric | 5 | 637 |
| distribucion_de_las_calcificaciones | distribucion_de_las_calcificaciones | numeric | 6 | 584 |
| presencia_de_asimetrias | presencia_de_asimetrias | numeric | 3 | 423 |
| tipo_de_asimetria | tipo_de_asimetria | numeric | 2 | 691 |
| hallazgos_asociados | hallazgos_asociados | numeric | 5 | 649 |
| lateralidad_hallazgo | lateralidad_hallazgo | numeric | 4 | 50 |
| birads | birads | numeric | 8 | 70 |
estructura_openai <- data.frame(
Variable = names(openai_data),
Clase = sapply(openai_data, class),
Valores_Únicos = sapply(openai_data, function(x) length(unique(x))),
Valores_NA = sapply(openai_data, function(x) sum(is.na(x)))
)
kable(estructura_openai, caption = "Estructura de las predicciones de OpenAI") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"))| Variable | Clase | Valores_Únicos | Valores_NA | |
|---|---|---|---|---|
| nodulos | nodulos | numeric | 3 | 1 |
| morfologia_nodulos | morfologia_nodulos | character | 5 | 581 |
| margenes_nodulos | margenes_nodulos | character | 7 | 581 |
| densidad_nodulo | densidad_nodulo | character | 6 | 586 |
| microcalcificaciones | microcalcificaciones | numeric | 3 | 1 |
| calcificaciones_benignas | calcificaciones_benignas | numeric | 13 | 144 |
| calcificaciones_sospechosas | calcificaciones_sospechosas | numeric | 4 | 686 |
| distribucion_calcificaciones | distribucion_calcificaciones | numeric | 6 | 571 |
| presencia_asimetrias | presencia_asimetrias | numeric | 3 | 1 |
| tipo_asimetria | tipo_asimetria | character | 6 | 473 |
| hallazgos_asociados | hallazgos_asociados | character | 14 | 421 |
| lateralidad_hallazgo | lateralidad_hallazgo | numeric | 3 | 0 |
| birads | birads | character | 11 | 14 |
La clasificación BIRADS es el principal criterio de evaluación radiológica, categorizada del 0 al 6:
# Crear un dataframe combinado para la comparación
birads_comp <- data.frame(
manual_birads = as.character(manual_data$birads),
openai_birads = as.character(openai_data$birads)
)
# Calcular porcentaje de coincidencias exactas
coincidencias_exactas <- sum(birads_comp$manual_birads == birads_comp$openai_birads, na.rm = TRUE)
porcentaje_exactas <- coincidencias_exactas / nrow(birads_comp) * 100
# Visualizar distribución de BIRADS en ambos conjuntos
manual_birads_dist <- birads_comp %>%
group_by(manual_birads) %>%
summarise(Count = n()) %>%
mutate(Dataset = "Manual") %>%
rename(BIRADS = manual_birads)
openai_birads_dist <- birads_comp %>%
group_by(openai_birads) %>%
summarise(Count = n()) %>%
mutate(Dataset = "OpenAI") %>%
rename(BIRADS = openai_birads)
# Combinar para gráfico de comparación
birads_combined <- rbind(manual_birads_dist, openai_birads_dist)
# Gráfico de barras para comparar distribución BIRADS
ggplot(birads_combined, aes(x = BIRADS, y = Count, fill = Dataset)) +
geom_bar(stat = "identity", position = position_dodge()) +
scale_fill_viridis(discrete = TRUE, option = "D", begin = 0.3, end = 0.7) +
labs(
title = "Comparación de distribución BIRADS: Manual vs OpenAI",
subtitle = paste0("Coincidencias exactas: ", coincidencias_exactas, " (", round(porcentaje_exactas, 2), "%)"),
x = "Categoría BIRADS",
y = "Cantidad de casos"
) +
theme_ipsum_rc() +
theme(
plot.title = element_text(face = "bold"),
legend.position = "top"
)Esta matriz muestra cómo el modelo clasifica los diferentes valores BIRADS en comparación con los datos manuales de referencia:
# Crear matriz de confusión para BIRADS
confusion_matrix <- birads_comp %>%
group_by(manual_birads, openai_birads) %>%
summarise(count = n(), .groups = "drop") %>%
spread(openai_birads, count, fill = 0)
# Visualizar como heatmap
confusion_long <- confusion_matrix %>%
pivot_longer(cols = -manual_birads, names_to = "openai_birads", values_to = "count")
ggplot(confusion_long, aes(x = openai_birads, y = manual_birads, fill = count)) +
geom_tile() +
scale_fill_viridis(option = "C", direction = -1) +
geom_text(aes(label = ifelse(count > 0, count, "")), color = "white", size = 3) +
labs(
title = "Matriz de confusión para clasificación BIRADS",
subtitle = "Manual (eje Y) vs OpenAI (eje X)",
x = "OpenAI BIRADS",
y = "Manual BIRADS",
fill = "Cantidad"
) +
theme_minimal() +
theme(
axis.text.x = element_text(angle = 45, hjust = 1),
panel.grid = element_blank()
)Una forma más interpretable clínicamente es agrupar las clasificaciones BIRADS en categorías funcionales:
# Crear categorías clínicas para BIRADS
birads_comp <- birads_comp %>%
mutate(
manual_cat = case_when(
manual_birads %in% c("1", "2") ~ "Benigno",
manual_birads == "3" ~ "Probablemente benigno",
manual_birads %in% c("4", "4A", "4B", "4C", "5", "6") ~ "Sospechoso/Maligno",
manual_birads == "0" ~ "Evaluación incompleta",
TRUE ~ "No especificado"
),
openai_cat = case_when(
openai_birads %in% c("1", "2") ~ "Benigno",
openai_birads == "3" ~ "Probablemente benigno",
openai_birads %in% c("4", "4A", "4B", "4C", "5", "6") ~ "Sospechoso/Maligno",
openai_birads == "0" ~ "Evaluación incompleta",
TRUE ~ "No especificado"
)
)
# Calcular coincidencias por categoría
cat_matches <- birads_comp %>%
group_by(manual_cat) %>%
summarise(
Total = n(),
Coincidencias = sum(manual_cat == openai_cat, na.rm = TRUE),
Porcentaje = round(Coincidencias / Total * 100, 2)
)
# Matriz de confusión para categorías
cat_matrix <- birads_comp %>%
group_by(manual_cat, openai_cat) %>%
summarise(Count = n(), .groups = "drop") %>%
pivot_wider(names_from = openai_cat, values_from = Count, values_fill = 0)
# Mostrar tabla
kable(cat_matrix, caption = "Matriz de confusión por categorías clínicas") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"))| manual_cat | Benigno | Evaluación incompleta | No especificado | Probablemente benigno | Sospechoso/Maligno |
|---|---|---|---|---|---|
| Benigno | 488 | 20 | 10 | 11 | 21 |
| Evaluación incompleta | 7 | 0 | 1 | 0 | 0 |
| No especificado | 63 | 2 | 1 | 1 | 3 |
| Probablemente benigno | 18 | 0 | 1 | 0 | 0 |
| Sospechoso/Maligno | 44 | 2 | 1 | 1 | 0 |
# Gráfico de barras apiladas para mostrar coincidencias por categoría
cat_confusion_long <- birads_comp %>%
group_by(manual_cat, openai_cat) %>%
summarise(Count = n(), .groups = "drop")
ggplot(cat_confusion_long, aes(x = manual_cat, y = Count, fill = openai_cat)) +
geom_bar(stat = "identity", position = "fill") +
scale_fill_viridis(discrete = TRUE) +
scale_y_continuous(labels = percent) +
labs(
title = "Concordancia por categorías clínicas BIRADS",
subtitle = "Proporción de clasificaciones de OpenAI para cada categoría manual",
x = "Categoría manual",
y = "Proporción",
fill = "Predicción OpenAI"
) +
theme_ipsum_rc() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))# Comparar detección de nódulos
nodulos_comp <- data.frame(
manual = manual_data$nodulos,
openai = openai_data$nodulos
)
# Coincidencias exactas
coincidencias_nodulos <- sum(nodulos_comp$manual == nodulos_comp$openai, na.rm = TRUE)
porcentaje_nodulos <- coincidencias_nodulos / nrow(nodulos_comp) * 100
# Matriz de confusión para nódulos
nodulos_matrix <- table(manual = nodulos_comp$manual, openai = nodulos_comp$openai)
nodulos_matrix_df <- as.data.frame(nodulos_matrix)
ggplot(nodulos_matrix_df, aes(x = openai, y = manual, fill = Freq)) +
geom_tile() +
geom_text(aes(label = Freq), color = "white") +
scale_fill_viridis() +
labs(
title = "Matriz de confusión: Detección de nódulos",
subtitle = paste0("Coincidencias exactas: ", coincidencias_nodulos, " (", round(porcentaje_nodulos, 2), "%)"),
x = "OpenAI",
y = "Manual",
fill = "Frecuencia"
) +
theme_minimal()# Comparar detección de microcalcificaciones
micro_comp <- data.frame(
manual = manual_data$presencia_microcalcificaciones,
openai = openai_data$microcalcificaciones
)
# Coincidencias exactas
coincidencias_micro <- sum(micro_comp$manual == micro_comp$openai, na.rm = TRUE)
porcentaje_micro <- coincidencias_micro / nrow(micro_comp) * 100
# Matriz de confusión para microcalcificaciones
micro_matrix <- table(manual = micro_comp$manual, openai = micro_comp$openai)
micro_matrix_df <- as.data.frame(micro_matrix)
ggplot(micro_matrix_df, aes(x = openai, y = manual, fill = Freq)) +
geom_tile() +
geom_text(aes(label = Freq), color = "white") +
scale_fill_viridis() +
labs(
title = "Matriz de confusión: Microcalcificaciones",
subtitle = paste0("Coincidencias exactas: ", coincidencias_micro, " (", round(porcentaje_micro, 2), "%)"),
x = "OpenAI",
y = "Manual",
fill = "Frecuencia"
) +
theme_minimal()# Comparar detección de asimetrías
asim_comp <- data.frame(
manual = manual_data$presencia_de_asimetrias,
openai = openai_data$presencia_asimetrias
)
# Coincidencias exactas
coincidencias_asim <- sum(asim_comp$manual == asim_comp$openai, na.rm = TRUE)
porcentaje_asim <- coincidencias_asim / nrow(asim_comp) * 100
# Matriz de confusión para asimetrías
asim_matrix <- table(manual = asim_comp$manual, openai = asim_comp$openai)
asim_matrix_df <- as.data.frame(asim_matrix)
ggplot(asim_matrix_df, aes(x = openai, y = manual, fill = Freq)) +
geom_tile() +
geom_text(aes(label = Freq), color = "white") +
scale_fill_viridis() +
labs(
title = "Matriz de confusión: Asimetrías",
subtitle = paste0("Coincidencias exactas: ", coincidencias_asim, " (", round(porcentaje_asim, 2), "%)"),
x = "OpenAI",
y = "Manual",
fill = "Frecuencia"
) +
theme_minimal()# Crear resumen de coincidencias para todas las características principales
calculate_matches <- function(manual_col, openai_col) {
matches <- sum(manual_col == openai_col, na.rm = TRUE)
percentage <- matches / length(manual_col) * 100
return(list(matches = matches, percentage = percentage))
}
# Lista de columnas a comparar
columns_to_compare <- list(
list(name = "BIRADS", manual = "birads", openai = "birads"),
list(name = "Nódulos", manual = "nodulos", openai = "nodulos"),
list(name = "Microcalcificaciones", manual = "presencia_microcalcificaciones", openai = "microcalcificaciones"),
list(name = "Calcificaciones benignas", manual = "calcificaciones_tipicamente_benignas", openai = "calcificaciones_benignas"),
list(name = "Calcificaciones sospechosas", manual = "calcififcaciones_morfologia_sospechosa", openai = "calcificaciones_sospechosas"),
list(name = "Distribución calcificaciones", manual = "distribucion_de_las_calcificaciones", openai = "distribucion_calcificaciones"),
list(name = "Presencia asimetrías", manual = "presencia_de_asimetrias", openai = "presencia_asimetrias"),
list(name = "Tipo asimetría", manual = "tipo_de_asimetria", openai = "tipo_asimetria"),
list(name = "Hallazgos asociados", manual = "hallazgos_asociados", openai = "hallazgos_asociados"),
list(name = "Lateralidad", manual = "lateralidad_hallazgo", openai = "lateralidad_hallazgo")
)
# Calcular coincidencias para cada par de columnas
results <- data.frame(
Característica = character(),
Coincidencias = numeric(),
Porcentaje = numeric(),
stringsAsFactors = FALSE
)
for (col in columns_to_compare) {
match_results <- calculate_matches(
manual_data[[col$manual]],
openai_data[[col$openai]]
)
results <- rbind(results, data.frame(
Característica = col$name,
Coincidencias = match_results$matches,
Porcentaje = round(match_results$percentage, 2)
))
}
# Ordenar resultados por porcentaje de coincidencia
results <- results[order(-results$Porcentaje), ]
# Gráfico de barras para visualizar coincidencias
ggplot(results, aes(x = reorder(Característica, Porcentaje), y = Porcentaje, fill = Porcentaje)) +
geom_bar(stat = "identity") +
geom_text(aes(label = paste0(Porcentaje, "%")), hjust = -0.1) +
scale_fill_viridis() +
coord_flip() +
labs(
title = "Porcentaje de coincidencias por característica radiológica",
subtitle = "Comparación Manual vs OpenAI",
x = "",
y = "Porcentaje de coincidencia (%)"
) +
theme_ipsum_rc() +
theme(legend.position = "none") +
scale_y_continuous(limits = c(0, 100))# Análisis de sobreestimaciones y subestimaciones de BIRADS
birads_numeric <- birads_comp %>%
filter(!is.na(manual_birads) & !is.na(openai_birads)) %>%
# Convertir valores especiales como "4A", "4B", "4C" a numéricos
mutate(
manual_numeric = case_when(
manual_birads == "4A" ~ 4.1,
manual_birads == "4B" ~ 4.5,
manual_birads == "4C" ~ 4.9,
TRUE ~ as.numeric(as.character(manual_birads))
),
openai_numeric = case_when(
openai_birads == "4A" ~ 4.1,
openai_birads == "4B" ~ 4.5,
openai_birads == "4C" ~ 4.9,
TRUE ~ as.numeric(as.character(openai_birads))
),
diferencia = openai_numeric - manual_numeric,
tipo_error = case_when(
diferencia > 0 ~ "Sobreestimación",
diferencia < 0 ~ "Subestimación",
TRUE ~ "Coincidencia exacta"
),
magnitud_error = abs(diferencia)
)
# Resumen de errores
resumen_errores <- birads_numeric %>%
group_by(tipo_error) %>%
summarise(
cantidad = n(),
porcentaje = round(n() / nrow(birads_numeric) * 100, 2),
promedio_magnitud = round(mean(magnitud_error, na.rm = TRUE), 2)
)
# Visualización
ggplot(birads_numeric, aes(x = manual_numeric, y = openai_numeric)) +
geom_jitter(aes(color = tipo_error), alpha = 0.7, width = 0.1, height = 0.1) +
geom_abline(slope = 1, intercept = 0, linetype = "dashed") +
scale_color_manual(values = c("Coincidencia exacta" = "#2ecc71",
"Sobreestimación" = "#e74c3c",
"Subestimación" = "#3498db")) +
labs(
title = "Comparación de BIRADS: Manual vs OpenAI",
subtitle = "Línea diagonal indica coincidencia perfecta",
x = "BIRADS Manual",
y = "BIRADS OpenAI",
color = "Tipo de error"
) +
theme_ipsum_rc() +
theme(legend.position = "top")# Distribución de errores
ggplot(birads_numeric, aes(x = diferencia, fill = tipo_error)) +
geom_histogram(binwidth = 0.5, color = "white", alpha = 0.8) +
scale_fill_manual(values = c("Coincidencia exacta" = "#2ecc71",
"Sobreestimación" = "#e74c3c",
"Subestimación" = "#3498db")) +
labs(
title = "Distribución de errores en clasificación BIRADS",
subtitle = "Valores negativos indican subestimación, positivos sobreestimación",
x = "Diferencia (OpenAI - Manual)",
y = "Cantidad de casos",
fill = "Tipo de error"
) +
theme_ipsum_rc()Este análisis es especialmente importante para evaluar el impacto clínico potencial de las clasificaciones erróneas:
# Clasificar las discrepancias por su impacto clínico
birads_impacto <- birads_comp %>%
filter(!is.na(manual_cat) & !is.na(openai_cat)) %>%
mutate(
impacto_clinico = case_when(
manual_cat == openai_cat ~ "Sin impacto",
manual_cat == "Benigno" & openai_cat == "Probablemente benigno" ~ "Bajo impacto",
manual_cat == "Probablemente benigno" & openai_cat == "Benigno" ~ "Bajo impacto",
manual_cat %in% c("Benigno", "Probablemente benigno") & openai_cat == "Sospechoso/Maligno" ~ "Sobrediagnóstico",
manual_cat == "Sospechoso/Maligno" & openai_cat %in% c("Benigno", "Probablemente benigno") ~ "Subdiagnóstico crítico",
TRUE ~ "Otro"
)
)
# Resumen de impacto clínico
impacto_summary <- birads_impacto %>%
group_by(impacto_clinico) %>%
summarise(
cantidad = n(),
porcentaje = round(n() / nrow(birads_impacto) * 100, 2)
) %>%
arrange(desc(porcentaje))
# Visualización
ggplot(impacto_summary, aes(x = reorder(impacto_clinico, -porcentaje), y = porcentaje, fill = impacto_clinico)) +
geom_bar(stat = "identity") +
geom_text(aes(label = paste0(porcentaje, "%")), vjust = -0.5) +
scale_fill_viridis(discrete = TRUE) +
labs(
title = "Impacto clínico de las discrepancias en clasificación BIRADS",
x = "",
y = "Porcentaje (%)"
) +
theme_ipsum_rc() +
theme(
axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "none"
)# Crear un resumen visual de los principales hallazgos
resumen_hallazgos <- data.frame(
Métrica = c(
"Coincidencia exacta BIRADS",
"Coincidencia por categoría clínica",
"Subdiagnóstico crítico",
"Sobrediagnóstico",
"Coincidencia en detección de nódulos",
"Coincidencia en microcalcificaciones"
),
Valor = c(
paste0(round(porcentaje_exactas, 1), "%"),
paste0(round(sum(birads_comp$manual_cat == birads_comp$openai_cat, na.rm = TRUE) /
sum(!is.na(birads_comp$manual_cat) & !is.na(birads_comp$openai_cat)) * 100, 1), "%"),
paste0(round(sum(birads_impacto$impacto_clinico == "Subdiagnóstico crítico", na.rm = TRUE) /
nrow(birads_impacto) * 100, 1), "%"),
paste0(round(sum(birads_impacto$impacto_clinico == "Sobrediagnóstico", na.rm = TRUE) /
nrow(birads_impacto) * 100, 1), "%"),
paste0(round(porcentaje_nodulos, 1), "%"),
paste0(round(porcentaje_micro, 1), "%")
)
)
kable(resumen_hallazgos, caption = "Resumen de hallazgos principales") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"), full_width = FALSE) %>%
row_spec(0, bold = TRUE, color = "white", background = "#4287f5")| Métrica | Valor |
|---|---|
| Coincidencia exacta BIRADS | 65.6% |
| Coincidencia por categoría clínica | 70.4% |
| Subdiagnóstico crítico | 6.5% |
| Sobrediagnóstico | 3% |
| Coincidencia en detección de nódulos | 64% |
| Coincidencia en microcalcificaciones | 31.7% |
Basándonos en el análisis comparativo entre el modelo OpenAI y los datos manuales de referencia, podemos concluir:
El modelo OpenAI muestra un potencial prometedor como herramienta de apoyo en la interpretación radiológica, pero las discrepancias identificadas, especialmente aquellas con potencial impacto clínico significativo, subrayan la importancia de mantener la supervisión experta. Los hallazgos de este análisis pueden servir como base para futuras iteraciones del modelo con el objetivo de mejorar su precisión diagnóstica y utilidad clínica.